﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;

using Cookbook.Common;

namespace Cooking.Client.CaliburnExtensions
{
    public class EditableScreen : Caliburn.Micro.Screen, INotifyDataErrorInfo
    {
        private readonly Dictionary<string, List<Violation>> violations = new Dictionary<string, List<Violation>>();

        /// <summary>
        /// Occurs when the validation errors have changed for a property or for the entire entity.
        /// </summary>
        /// <remarks></remarks>
        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged = delegate { }; 

        /// <summary>
        /// Gets a value that indicates whether the entity has validation errors. 
        /// </summary>
        /// <returns>
        /// true if the entity currently has validation errors; otherwise, false.
        /// </returns>
        public bool HasErrors
        {
            get { return violations.Keys.Any(BrokenRuleExistsFor); }
        }
        
        private bool BrokenRuleExistsFor(string property)
        {
            return GetViolationsForProperty(property).Count > 0;
        }

        public bool Validate<TProperty>(Expression<Func<TProperty>> property, Rule rule, Func<bool> condition)
        {
            if (!condition())
            {
                AddBrokenRule(property, rule);
                return false;
            } 
            else
            {
                RemoveBrokenRule(property, rule);
                return true;
            }
        }

        protected void AddBrokenRule<TProperty>(Expression<Func<TProperty>> property, Rule rule)
        {
            List<Violation> violations = GetViolationsForProperty(property.GetMemberInfo().Name);

            if (!violations.Any(r => r.Rule == rule))
            {
                violations.Add(new Violation(rule));
                ErrorsChanged(this, new DataErrorsChangedEventArgs(property.GetMemberInfo().Name));
            }
        }

        protected void RemoveBrokenRule<TProperty>(Expression<Func<TProperty>> property, Rule rule)
        {
            List<Violation> violations = GetViolationsForProperty(property.GetMemberInfo().Name);

            Violation violation = violations.SingleOrDefault(br => br.Rule == rule);
            if (violation != null)
            {
                violations.Remove(violation);
                ErrorsChanged(this, new DataErrorsChangedEventArgs(property.GetMemberInfo().Name));
            }
        }

        /// <summary>
        /// Gets the validation errors for a specified property or for the entire entity.
        /// </summary>
        /// <returns>
        /// The validation errors for the property or entity.
        /// </returns>
        /// <param name="propertyName">The name of the property to retrieve validation errors for; or null or <see cref="F:System.String.Empty"/>, to retrieve entity-level errors.</param>
        public IEnumerable GetErrors(string propertyName)
        {
            if (BrokenRuleExistsFor(propertyName))
            {
                return GetViolationsForProperty(propertyName).Select(v => v.Rule.Code).ToArray();
            }
            else
            {
                return null;
            }
        }

        private List<Violation> GetViolationsForProperty(string propertyName)
        {
            if (!violations.ContainsKey(propertyName))
            {
                violations[propertyName] = new List<Violation>();
            }

            return violations[propertyName];
        }

        internal class Violation
        {
            public Violation(Rule rule)
            {
                Rule = rule;
            }

            public Rule Rule { get; set; }
        }
    }
}
